 /**********************************************************************
 * Fichier : USBDriva.c
 *
 * Résumé : Fichier source principal du pilote. Défini les fonctions
 *          usager du module.
 *
 * Auteur : Vincent Léger
 *
 * Dernière modification : dimanche 9 décembre 2012
 *
 **********************************************************************/

 /******************************************
 *********DÉCLARATION DE LIBRAIRIES*********
 ******************************************/
#include "USBDriva.h"           //déclaration des fonctions usager et variables globales
#include "USBDrivaCore.h"       //fonctions internes et structures personnelles
 /******************************************
 **********INFORMATIONS DU MODULE***********
 ******************************************/
MODULE_AUTHOR("V. Leger, F. Ramos");
MODULE_LICENSE("Dual BSD/GPL");

/******************************************
*********DÉFINITIONS DE FONCTIONS**********
******************************************/

 /**********************************************************************
 *  Fonction : USBDriva_init
 *
 *  Résumé : Fonction d'initialisation et d'installation du pilote.
 *
 *  Entrées : - aucune (void)
 *
 *  Sorties : - aucune (void)
 *
 *  Valeur de retour : Int --> 0     = succès
 *                         --> autre = erreur (code d'erreur usb_register)
 **********************************************************************/
int USBDriva_init (void)
{
    //variables
    int result; //résultat de usb_register
    int i;      //compteur de boucle

    result = usb_register(&USBDriva);   //enregistrement dans USBCORE
    if (result)
        DEBUG_VALUE("ERREUR avec usb_register. Code = ", result);
    else
        DEBUG_MESSAGE("usb_register SUCCESS !!!");

    for(i=0;i<NB_URB;i++)   //initialisation des URBs
        myUrb[i] = NULL;
    return result;
}

/**********************************************************************
*  Fonction : USBDriva_exit
*
*  Résumé : Fonction de désinstallation du module.
*
*  Entrées : - aucune (void)
*
*  Sorties : - aucune (void)
*
*  Valeur de retour : Aucune (void)
**********************************************************************/
void USBDriva_exit (void)
{
    usb_deregister(&USBDriva);  //désenregistrement dans le USBCORE
}

/**********************************************************************
*  Fonction : driva_probe
*
*  Résumé : Fonction PROBE pour module USB. Utiliser par USBCORE pour
*           se connecter à la caméra lorsqu'elle est branchée.
*
*  Entrées : - struct usb_interface *interface
*                   --> interface du device qui tente de se connecter
*            - const struct usb_device_id *id
*                   --> identification du device qui tente de se connecter
*
*  Sorties : - aucune
*
*  Valeur de retour : int  --> 0       = succès, interface capturée
*                          --> -1      = ERREUR, mauvais ID ou interface
*                          --> -ENOMEM = ERREUR, manque de mémoire
**********************************************************************/
static int driva_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
    //déclaration et initialisation des variables
    struct usbdriva_dev* MyDev = NULL;  //structure personnelle
    struct usb_device* device = NULL;   //device USB (la cam dans le cas présent)
    int err = 0;                        //gestion d'erreurs
    char temp = RESET_CMD;              //pour passer la commande RESET du pan/titl

    DEBUG_VALUE("INIT: idProduct : ", id->idProduct);
    DEBUG_VALUE("INIT: idVendor : ", id->idVendor);
    DEBUG_VALUE("CLASS: ", interface->cur_altsetting->desc.bInterfaceClass);
    DEBUG_VALUE("SUB-CLASS: ", interface->cur_altsetting->desc.bInterfaceSubClass);
    //verification du produit (redondant... mais robuste)
    if ((id->idVendor != DRIVA_VENDOR_ID) || (id->idProduct != DRIVA_PROD_ID)) return -1;
    //interface video ??? sinon on capture pas
    if (interface->cur_altsetting->desc.bInterfaceClass != CC_VIDEO) return -1;
    //verfifie la sous-classe... si ce n'est pas VIDEO STREAMING: on capture, mais on fait rien
    if (interface->cur_altsetting->desc.bInterfaceSubClass != SC_VIDEOSTREAMING) return 0;

    DEBUG_MESSAGE("INIT: Allocation de memoire pour la structure du device");
    MyDev = kmalloc(sizeof(struct usbdriva_dev), GFP_KERNEL);   //création de la structure perso
    if (MyDev == NULL)
    {
        DEBUG_MESSAGE("INIT: Manque de memoire pour creer la structure !");
        kfree(MyDev);
        return -ENOMEM;
    }
    DEBUG_MESSAGE("INIT: Memset...");
    memset(MyDev, 0, sizeof(struct usbdriva_dev));  //initialisation des valeurs

    DEBUG_MESSAGE("INIT: get device (interface to usbdev)");
    device = interface_to_usbdev(interface); //cherche le device à partir de l'interface

    DEBUG_MESSAGE("INIT: usb get dev");
    MyDev->udev = usb_get_dev(device);      //trouve et copie une référence au DEVICE
                                            //dans la structure perso
    MyDev->interface = interface;           //copie un pointeur vers l'interface

    //initialization de la synchronisation (GRAB/READ)
    init_completion (&MyDev->callback);

    DEBUG_MESSAGE("INIT: set interface");
    usb_set_intfdata(interface, MyDev); //enregistrement de l'interface/device dans USBCORE

    //inscription de la classe dans le usb register
    DEBUG_MESSAGE("INIT: register dev");
    err = usb_register_dev(interface, &usbdriva_class);
    if (err) {
        DEBUG_MESSAGE("INIT: Pas capable d'attribuer le minor");
        usb_set_intfdata(interface, NULL);  //desalocation du device
        usb_put_dev(MyDev->udev);
        kfree(MyDev);
        return -ENOMEM;    //TODO: find better return codes... return err ???
    }
    MyDev->isOpen = false;  //aucun usager n'a ouvert...
    //initialisation de semaphores
    sema_init(&MyDev->SemRead, 0);
    sema_init(&MyDev->SemGrab, 0);

    DEBUG_MESSAGE("INIT: inscription de la classe dans le usb register ");
    DEBUG_VALUE("INIT: PROBE termine... minor = ", interface->minor);
    //configuration du alternate setting (le choix est hard-codé)
    usb_set_interface((MyDev->udev), 1, 4);

    //COMMANDES D'INITIALISATION
    //ferme le streaming (qui s'active par défaut)
    usb_control_msg (MyDev->udev,
                            usb_sndctrlpipe(MyDev->udev, 0),
                            0X0B,
                            USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE,
                            0x0000, 0x0001, NULL, 0, 0);

    //initialise la position du Pan/Tilt
     usb_control_msg (MyDev->udev,
                            usb_sndctrlpipe(MyDev->udev, 0),
                            0X01,
                            (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE),
                            0x0200, 0x0900, &temp, 1, 0);

    return 0;   //SUCCESS
}

/**********************************************************************
*  Fonction : driva_disconnect
*
*  Résumé : Fonction DISCONNECT pour module USB. Géré par le USBCORE
*           lors de la déconnexion du device.
*
*  Entrées : - struct usb_interface *interface
*                   --> interface du device qui se déconnecte
*
*  Sorties : - aucune
*
*  Valeur de retour : aucune (void)
**********************************************************************/
static void driva_disconnect(struct usb_interface *interface)
{
    //déclaration/initialisation des variables
    struct usbdriva_dev *MyDev = NULL;  //structure perso
    int minor = interface->minor;       //MINOR
    unsigned int i = 0;                 //compteur de boucle

    DEBUG_VALUE("DECONNEXION... minor = ", minor);

    //Si ce n'est pas l'interface streaming, faut relâcher mais sans rien faire.
    if (interface->cur_altsetting->desc.bInterfaceSubClass != SC_VIDEOSTREAMING) return;

    //on tue les urb...
    if (myUrb[i] != NULL)
    {
    	for (i=0;i<NB_URB;i++)  //destruction des URBs
    	{
    		DEBUG_VALUE("USBREAD: URB #",i);

            usb_kill_urb(myUrb[i]); //signal la mort du URB
            if(myUrb[i]->status == -ENOENT)
            {
            DEBUG_VALUE("FAILED TO KILL URB #",i);
//               	return -ENOENT;
            }
            //desallocation du tampon de transfert
            usb_free_coherent(MyDev->udev,
                              myUrb[i]->transfer_buffer_length,
                              myUrb[i]->transfer_buffer,
                              myUrb[i]->transfer_dma);
             //juste au cas...
             myUrb[i]->transfer_buffer_length = 0;
             myUrb[i]->transfer_flags = URB_FREE_BUFFER;
             //relâche le URB
             usb_free_urb(&myUrb[i]);

             if(myUrb[i] == NULL)
             DEBUG_VALUE("USBREAD: freed URB #", i);
           }

       }

    MyDev = usb_get_intfdata(interface); //recupération du de la structure perso
    //desalocation de l'interface
    usb_set_intfdata(interface, NULL);
    //désenregistrement de la classe
    usb_deregister_dev(interface, &usbdriva_class);
    //désinstallation du device
    usb_put_dev(MyDev->udev);
    //relâche la mémoire de la structure perso
    kfree(MyDev);
    DEBUG_MESSAGE("DECONNEXION COMPLETEE");
}

/**********************************************************************
*  Fonction : USBDriva_open
*
*  Résumé : Fonction d'ouverture du pilote par l'usager.
*           Donne le contrôle unique du pilote à l'usager et associe
*           le private data à l'interface vidéo streaming.
*
*  Entrées : - struct inode *inode
*                   --> contient les informations du device
*            - struct file *filp
*                   --> structure file operation de l'usager
*
*  Sorties : - aucune
*
*  Valeur de retour : int --> 0         = succès, pilote ouvert
*                             -ENODEV   = ERREUR, trouve pas interface/device
*                             -1        = ERREUR, pilote déjà ouvert
**********************************************************************/
static int USBDriva_open(struct inode *inode, struct file *filp)
{
    struct usb_interface *intf = NULL;
    struct usbdriva_dev *MyDev = NULL;
    int subminor;
    DEBUG_MESSAGE("OUVERTURE...");
    subminor = iminor(inode);   //minor à partir de la structure inode

    intf = usb_find_interface(&USBDriva, subminor); //on retrouve l'interface
    if (!intf)
    {
        DEBUG_MESSAGE("PAS CAPABLE DE TROUVER L'INTERFACE");
        return -ENODEV;
    }

    MyDev = usb_get_intfdata(intf); //retrouve la structure perso
    if (MyDev == NULL)
        return -ENODEV;

    if (MyDev->isOpen == true)  //si déjà ouvert
        return -1;  //refuse l'accès

    MyDev->isOpen = false;      //on bloque tout autre ouverture
    filp->private_data = intf;  //association de l'interface à private data

    return 0;
}

/**********************************************************************
*  Fonction : USBDriva_release
*
*  Résumé : Fonction de fermeture du pilote par l'usager.
*           Relâche le contrôle unique du pilote à l'usager.
*
*  Entrées : - struct inode *inode
*                   --> contient les informations du device
*            - struct file *filp
*                   --> structure file operation de l'usager
*
*  Sorties : - aucune
*
*  Valeur de retour : int --> 0         = succès, pilote ouvert
*                             -ENODEV   = ERREUR, trouve pas interface/device
**********************************************************************/
static int USBDriva_release(struct inode *inode, struct file *filp)
{
    struct usbdriva_dev *MyDev = NULL;
    struct usb_interface *intf = NULL;

    intf = (struct usb_interface *)filp->private_data;  //recupere l'interface

    if (intf == NULL)
        return -ENODEV;

    MyDev = usb_get_intfdata(intf); //trouve la structure personnelle
    if (MyDev == NULL)
        return -ENODEV;

    MyDev->isOpen = false;  //permet à quelqu'un d'autre d'ouvrir le pilote

    filp->private_data = NULL;  //
    DEBUG_MESSAGE("FERMETURE COMPLETE");
    return 0;
}

/**********************************************************************
*  Fonction : USBDriva_read
*
*  Résumé : Fonction de lecture du pilote par l'usager.
*           Lit les valeurs reçu par les URBs asynchrones.
*
*  Entrées : - struct file *filp
*                   --> structure file operation de l'usager
*            - size_t count
*                   --> nombre de caractères demandés par l'usager
*                       (pas utilisé)
*            - loff_t *f_pos
*                   --> offset pour la lecture (pas utilisé)
*
*  Sorties : - char __user *ubuf
*                   --> tampon de réception de l'usager
*
*  Valeur de retour : ssize_t --> ­>=0     = nombres de caractès lus
*                                 -EFAULT = ERREUR, erreur URB ou copy_to_user
*
*
*  note : DOIT ÊTRE UTILISÉ APRÈS UN GRAB !!!
**********************************************************************/
ssize_t USBDriva_read (struct file *filp, char __user *ubuf, size_t count, loff_t *f_pos)
{
    //déclaration/initialisation des variables
    struct usbdriva_dev *MyDev = NULL;  //structure personnelle
    int returnValue = 0;    //valeur de retour (en cas d'erreurs)
    int i = 0;              //compteur de boucles
    int qtyRead = 0;        //quantitée de caractès lus
    struct usb_interface *intf = filp->private_data;

    DEBUG_MESSAGE("USBREAD: READING...");

    if (intf != NULL)
    {
    	DEBUG_MESSAGE("USBREAD: intf != NULL");
        MyDev = usb_get_intfdata(intf); //récupère la structure personnelle
    }
    if (MyDev == NULL)
    {
    	DEBUG_MESSAGE("USBREAD: MyDev == NULL");
        return -ENODEV;
    }

    //Pour empêcher un dead-lock si un grab n'a pas été lancé...
    if(down_trylock(&MyDev->SemRead))
    {
    	DEBUG_MESSAGE("USBREAD: GRAB NOT LUNCHED");
    	return -ENOEXEC;
    }
    //attente de synchronisation avec le GRAB
    wait_for_completion (&MyDev->callback);

    DEBUG_VALUE("USBREAD: sentURB =", sentURB); //for now just check if everything is fine...
    DEBUG_VALUE("USBREAD: actual length: ",myLengthUsed);
    //envoie des données à l'usager
    qtyRead = copy_to_user(ubuf, myData, myLengthUsed);

    DEBUG_VALUE("USBREAD: qtyRead: ", qtyRead);

    if (qtyRead<0)  //valeur de retour
        returnValue = -EFAULT;
    else
        returnValue = myLengthUsed;

    DEBUG_VALUE("USBREAD: returnValue =  ", myLengthUsed);

    for (i=0;i<NB_URB;i++)  //destruction des URBs
    {
        if (myUrb[i] == NULL)
            return -EFAULT;
        DEBUG_VALUE("USBREAD: URB #",i);

        usb_kill_urb(myUrb[i]); //signal la mort du URB
        if(myUrb[i]->status == -ENOENT)
        {
            DEBUG_VALUE("FAILED TO KILL URB #",i);
        	return -ENOENT;
        }
        //desallocation du tampon de transfert
        usb_free_coherent(MyDev->udev,
                            myUrb[i]->transfer_buffer_length,
                            myUrb[i]->transfer_buffer,
                            myUrb[i]->transfer_dma);
        //juste au cas...
        myUrb[i]->transfer_buffer_length = 0;
        myUrb[i]->transfer_flags = URB_FREE_BUFFER;
        //relâche le URB
        usb_free_urb(&myUrb[i]);

        myUrb[i] = NULL;

        if(myUrb[i] == NULL)
            DEBUG_VALUE("USBREAD: freed URB #", i);
    }
    DEBUG_MESSAGE("USBREAD: Lecture terminee...");
    return returnValue;
}

/**********************************************************************
*  Fonction : USBDriva_ioctl
*
*  Résumé : Fonction de lecture du pilote par l'usager.
*           Lit les valeurs reçu par les URBs asynchrones.
*
*  Entrées : - struct file *filp
*                   --> structure file operation de l'usager
*            - unsigned int cmd
*                   --> valeur de la commande de l'usager
*            - unsigned long arg
*                   --> argument de la commande de l'usager
*
*  Sorties : aucune
*
*  Valeur de retour : long --> ­-ENODEV = ERREUR, trouve pas structure perso/interface
*                              -ENOENT =
*                              -1      = commande ou argument invalide
*                              autre   = valeur de retour de la commande par la CAM
*
* note: Les fonctions sont définis par le cahier de charge et sont
*       disponibles dans MyIOCTL.h. Les fonctions Pan/Tilt sont définies
*       dans DrivaDefinitions.h.
**********************************************************************/
long USBDriva_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
    //déclaration/initialisation des variables
    struct usbdriva_dev *MyDev = NULL;  //structure perso
    struct usb_interface *intf = NULL;  //interface du device
    unsigned char move_to_cam[PAN_TILT_CMDLENGTH]; //tableau local pour les commande de mouvenemt
    int retval = 0;                     //valeur de retour
    int i;                              //compteur de boucle

    intf = filp->private_data;  //recupère l'interface
    DEBUG_MESSAGE("USBIOCTL: command received");
    if (intf != NULL)
        MyDev = usb_get_intfdata(intf); //récupère la structure perso
    if (MyDev == NULL)
        return -ENODEV;
    DEBUG_MESSAGE("USBIOCTL: device found...");

    //verification des permissions ? de la commande ? direction ? NON !!!

    switch(cmd) //Traitement de la commande
    {
        case IOCTL_STREAMON:    //STREAM-ON
        {
            retval = usb_control_msg (MyDev->udev,
                                      usb_sndctrlpipe(MyDev->udev, 0),
                                      0X0B,
                                      (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE),
                                      0x0004, 0x0001, NULL, 0, 0);
            DEBUG_VALUE("IOCT_STREAMON = ", retval);
            //Release SemGrab only after having set stream to on
            //to prevent a blocking on caused by the fact that no valid callback value is returned
            //from the cam
            up(&MyDev->SemGrab);
            break;
        }
        case IOCTL_STREAMOFF:   //STREAM-OFF
        {
            retval = usb_control_msg (MyDev->udev,
                                      usb_sndctrlpipe(MyDev->udev, 0),
                                      0X0B,
                                      USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE,
                                      0x0000, 0x0001, NULL, 0, 0);
            DEBUG_VALUE("IOCTL_STREAMOFF = ", retval);
            break;
        }

        case IOCTL_PANTILT: //PANTILT CMD
        {
            DEBUG_VALUE("IOCTL_PANTILT command user = ", arg);
            switch (arg)
            {
                case 1: //up
                {
                	for (i=0 ; i<PAN_TILT_CMDLENGTH; i++)
                	{
                		move_to_cam[i] = movement.up[i];
                	}

                    break;
                }
                case 2: //down
                {
                	for (i=0 ; i<PAN_TILT_CMDLENGTH; i++)
                	{
                		move_to_cam[i] = movement.down[i];
                	}
                    break;
                }
                case 3: //left
                {
                	for (i=0 ; i<PAN_TILT_CMDLENGTH; i++)
                	{
                		move_to_cam[i] = movement.left[i];
                	}
                    break;
                }
                case 4: //right
                {
                	for (i=0 ; i<PAN_TILT_CMDLENGTH; i++)
                	{
                		move_to_cam[i] = movement.right[i];
                	}
                    break;
                }
                default:
                {
                    retval = -1;
                    break;
                }
            }
            if (retval >=0)
            {

            	retval = usb_control_msg (MyDev->udev,
                                        usb_sndctrlpipe(MyDev->udev, 0),
                                        0X01,
                                        (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE),
                                        0x0100, 0x0900, move_to_cam, 4, 0);

            }

            DEBUG_VALUE("retval = ", retval);
            break;
        }
        case IOCTL_PANTILT_RESET:   //PAN/Tilt reset
        {
            char temp = RESET_CMD;  //variable utilisé pour passer la commande de reset
            retval = usb_control_msg (MyDev->udev,
                                    usb_sndctrlpipe(MyDev->udev, 0),
                                    0X01,
                                    (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE),
                                    0x0200, 0x0900, &temp, 1, 0);
            DEBUG_VALUE("retval = ", retval);
            break;
        }
        case IOCTL_GET: //GET VALUES
        {
        	char request;//information demandée
        	short value; //valeur demandée (min, max, range ou value)
        	short data;  //valeur retournée

        	DEBUG_VALUE("arg = ",(int)arg);
        	//decode la requête
        	request = (char) (0x000000ff & (arg>>16));
        	//printk(KERN_WARNING "request = %#x\n",request);
        	//decole la valeur demandée
        	value = (short) (0x0000ffff & arg); //
        	//printk(KERN_WARNING "value = %#x\n",value);
        	data = 0;
        	retval = usb_control_msg (MyDev->udev,
                                  usb_rcvctrlpipe(MyDev->udev, 0),
                                  request,
                                  (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE),
                                  value , 0x0200, &data, 2, 0);
        	if (retval >=0) //si pas d'erreur
        		retval = data;  //on retourne la valeur
            DEBUG_VALUE("retval = ", retval);
            break;
        }
        case IOCTL_SET: //SET VALUES
        {
        	short value; // valeur à modifier
        	short data;  // valeur demandée

        	//printk(KERN_WARNING "arg = %#x\n",(int)arg);
        	//décode la demande de l'usager
        	value = (short) (0x000000ff & (arg>>24));
        	value = (value<<8) & 0xff00;
        	//printk(KERN_WARNING "value = %#x\n",value);
        	//décode la nouvelle valeur de l'usager
        	data = (short) (0x00ffffff & arg);
        	//printk(KERN_WARNING "data = %#x\n",data);
        	retval = usb_control_msg (MyDev->udev,
        	                          usb_sndctrlpipe(MyDev->udev, 0),
        	                          0x01,
        	                          (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE),
        	                           value, 0x0200, &data, 2, 0);
        	            DEBUG_VALUE("retval = ", retval);
        	            break;
        }
        case IOCTL_GRAB:    //GRAB !!!
        {
        	DEBUG_MESSAGE("IOCTL: GRAB");
            // check if stream has as been set to ON
            //User cannot lunch Grab if the stream hasnt been set to ON
            if(down_trylock(&MyDev->SemGrab))
            {
            	DEBUG_MESSAGE("STREAMON NOT LUNCHED");
            	return -ENOEXEC;
            }
            myLengthUsed = 0;       //init le nombre de caractères reçus
            retval = initURB(intf); //initialise les URBs

            //Release SemRead only after having initialised and submitted the URBs
            //to prevent a blocking on the wait_for_completion on USBDriva_read
            up(&MyDev->SemRead);
            DEBUG_VALUE("retval : ", retval);
            break;
        }

        case IOCTL_DESTROY_URB: //DEMANDE DE DESTRUCTION DES URBs
        {                       //Utilisé pour débug
        	DEBUG_MESSAGE("IOCTL: DESTROY_URB");
        	for (i=0;i<NB_URB;i++) //Pour chaque URB
            {
                if (myUrb[i] == NULL)
                    return -EFAULT;
                DEBUG_VALUE("IOCTL_DESTROY_URB: URB #",i);
                usb_kill_urb(myUrb[i]); //déclare la mort du URB
                if(myUrb[i]->status == -ENOENT)
                {
                    DEBUG_VALUE("IOCTL_DESTROY_URB: Fail to kill URB#",i);
                    return -ENOENT;
                }
                //relâche le tampon de transfert
                usb_free_coherent(MyDev->udev,
                                    myUrb[i]->transfer_buffer_length,
                                    myUrb[i]->transfer_buffer,
                                    myUrb[i]->transfer_dma);
                DEBUG_MESSAGE("IOCTL_DESTROY_URB: usb_free_coherent done");
                //relâche le URB
                usb_free_urb(&myUrb[i]);
                DEBUG_MESSAGE("IOCTL_DESTROY_URB: usb_free_urb done");
                if(myUrb[i] == NULL)
                    DEBUG_VALUE("FREED URB #", i);
            }
        	DEBUG_MESSAGE("ALL URB DESTROYED");
            DEBUG_VALUE("retval : ", retval);
            break;
        }

        default:
        {
            DEBUG_MESSAGE("INVALID CMD !?!");
            retval = -1;
            break;
        }
    }
    DEBUG_VALUE("RETURNING : ",retval);
    return retval;
}


